#include "basic_integration.hpp"
#include "connection_loop.hpp"
#include <croco/os/pipe.hpp>
#include <croco/os/select.hpp>
#include <croco/ssh/ssh.hpp>
#include <croco/ip/tcp.hpp>
#include <croco/os/thread_sync.hpp>
#include <croco/version.hpp>
#include <boost/shared_ptr.hpp>
#include <csignal>
#include <vector>
#include <list>
#include <iostream>

/// GLOBAL process terminator
croco::os::pipe* global_terminator = 0;

struct server_config
{
    server_config(const std::string& sAddress, int sPort, std::size_t sMaxConnections)
    : address(sAddress), port(sPort), maxConnections(sMaxConnections)
    {
    }

    std::string address;
    int port;
    std::size_t maxConnections;
};

void server_main(const server_config& config, const std::vector<int>& terms = std::vector<int>())
{
    typedef std::list< boost::shared_ptr<croco::os::thread_sync> > con_threads;

    basic_integration basicSSH;
    basicSSH.add_user("u1", "p1");
    basicSSH.add_user("u2", "p2");

    con_threads connections;
    croco::ip::server_tcp serverSock(config.address.c_str(), config.port);
    serverSock.listen();
    while(true)
    {
        croco::os::select_fds fds;
        fds.add_read(serverSock.native_handle());
        for(std::size_t i = 0, end = terms.size(); i < end; i++)
        {
            fds.add_interrupt(terms[i]);
        }
        std::set<croco::os::native_handle_type> signaled = croco::os::do_select(fds);
        if(signaled.find(serverSock.native_handle()) != signaled.end())
        {
            // new connection
            if(connections.size() == config.maxConnections)
            {
                // dropping incoming connection by accepting it and destroying
                serverSock.accept();
            }
            else
            {
                boost::shared_ptr<croco::os::thread_sync> sync(new croco::os::thread_sync);
                boost::thread t(connection_thread, boost::ref(serverSock), boost::ref(*sync), boost::ref(basicSSH), terms);
                sync->set_thread(t);
                try
                {
                    sync->wait_for_start();
                    connections.push_back(sync);
                }
                catch(...)
                {
                    std::cerr << "Unable to start connection thread: " << boost::current_exception_diagnostic_information() << std::endl;
                }
            }
        }
        if(fds.interrupted(signaled))
        {
            std::cout << "server stop requested" << std::endl;
            break;
        }
    }

    // NOTE: for now the collection keeps all the connection threads, even those already finished.
    // A potential improvement: implement a joined-thread collector - a separate thread that monitors
    // the threads collections and remove already finished ones.
    for(con_threads::iterator i = connections.begin(); i != connections.end(); ++i)
    {
        try
        {
            (*i)->join();
            std::cout << "connection thread joined" << std::endl;
        }
        catch(...)
        {
            std::cerr << boost::current_exception_diagnostic_information() << std::endl;
        }
    }
}

void stop_server(int sig)
{
    std::cout << "Signal #" << sig << " received. Exiting..." << std::endl;
    if(global_terminator)
    {
        global_terminator->write("q", 1);
    }
}

int main(int argc, char** argv)
{
    std::cout << "Multithreaded libssh based SSH server. Version " << CROCO_VERSION_STR << std::endl;

    const std::string SERVER_ADDRESS = "127.0.0.1";
    const int SERVER_PORT = 12345;
    const std::size_t MAX_SERVER_CONNECTIONS = 10;

    try
    {
        croco::os::pipe terminator;
        global_terminator = &terminator;
        ::signal(SIGTERM, stop_server);
        ::signal(SIGINT, stop_server);
        std::vector<int> terms;
        terms.push_back(terminator.r());
        server_config config(SERVER_ADDRESS, SERVER_PORT, MAX_SERVER_CONNECTIONS);
        server_main(config, terms);
        std::cout << "Server finished" << std::endl;
    }
    catch(...)
    {
        std::cerr << boost::current_exception_diagnostic_information() << std::endl;
        return 1;
    }
    return 0;
}
